1 Pseudotime analysis

1.1 Learning objectives

library(SingleCellExperiment)
library(scran)
library(scater)
library(batchelor)
library(cowplot)
library(pheatmap)
library(tidyverse)
library(SingleR)
library(destiny)
library(gam)
library(viridis)
library(msigdbr)
library(clusterProfiler)
#library(cellAlign) # https://github.com/shenorrLab/cellAlign
#library(Cairo)

1.2 Extract T-cells from HCA ABMMC Dataset

In this section, we are starting our analysis with normalized HCA data and perform integration, clustering and dimensionality reduction. Our aim is to extract T-cells from this dataset and proceed with pseudotime analysis in the next section.

We are going to work with HCA data. This data set has been pre-processed and normalized before.

sce <- readRDS(\../CourseMaterials/Robjects/hca_sce_nz_postDeconv_5kCellPerSpl.Rds\)

We use symbols in place of ENSEMBL IDs for easier interpretation later.

rownames(sce) <- uniquifyFeatureNames(rowData(sce)$ensembl_gene_id, names = rowData(sce)$Symbol)

1.2.1 Variance modeling

We block on the donor of origin to mitigate batch effects during highly variable gene (HVG) selection. We select a larger number of HVGs to capture any batch-specific variation that might be present.

dec.hca <- modelGeneVar(sce, block=sce$Sample.Name)
top.hca <- getTopHVGs(dec.hca, n=5000)

1.2.2 Data integration

The batchelor package provides an implementation of the Mutual Nearest Neighbours (MNN) approach via the fastMNN() function. We apply it to our HCA data to remove the donor specific effects across the highly variable genes in top.hca. To reduce computational work and technical noise, all cells in all samples are projected into the low-dimensional space defined by the top d principal components. Identification of MNNs and calculation of correction vectors are then performed in this low-dimensional space. The corrected matrix in the reducedDims() contains the low-dimensional corrected coordinates for all cells, which we will use in place of the PCs in our downstream analyses. We store it in ‘MNN’ slot in the main sce object.

set.seed(1010001)
merged.hca <- fastMNN(sce,
              batch = sce$Sample.Name,
              subset.row = top.hca)
reducedDim(sce, 'MNN') <- reducedDim(merged.hca, 'corrected')

1.2.3 Dimensionality Reduction

We cluster on the low-dimensional corrected coordinates to obtain a partitioning of the cells that serves as a proxy for the population structure. If the batch effect is successfully corrected, clusters corresponding to shared cell types or states should contain cells from multiple samples. We see that all clusters contain contributions from each sample after correction.

set.seed(01010100)
sce <- runPCA(sce, dimred=\MNN\)
sce <- runUMAP(sce, dimred=\MNN\)
sce <- runTSNE(sce, dimred=\MNN\)
plotPCA(sce, colour_by=\Sample.Name\) + ggtitle(\PCA\)

plotTSNE(sce, colour_by=\Sample.Name\) + ggtitle(\tSNE\)

plotUMAP(sce, colour_by=\Sample.Name\) + ggtitle(\UMAP\)

1.2.4 Clustering

Graph-based clustering generates an excessively large intermediate graph so we will instead use a two-step approach with k-means. We generate 1000 small clusters that are subsequently aggregated into more interpretable groups with a graph-based method.

set.seed(1000)
clust.hca <- clusterSNNGraph(sce,
                             use.dimred=\MNN\,
                             use.kmeans=TRUE,
                             kmeans.centers=1000)
colLabels(sce) <- factor(clust.hca)
table(colLabels(sce))

    1     2     3     4     5     6     7     8     9    10    11 
 4471  6365  2911  4420  2402 12536  1176  3566  1146   596   411 
plotPCA(sce, colour_by=\label\) + ggtitle(\PCA\)

plotUMAP(sce, colour_by=\label\) + ggtitle(\UMAP\)

plotTSNE(sce, colour_by=\label\) + ggtitle(\tSNE\)

1.2.5 Cell type classification

We perform automated cell type classification using a reference dataset to annotate each cluster based on its pseudo-bulk profile. This is for a quick assignment of cluster identity. We are going to use Human Primary Cell Atlas (HPCA) data for that. HumanPrimaryCellAtlasData function provides normalized expression values for 713 microarray samples from HPCA (Mabbott et al., 2013). These 713 samples were processed and normalized as described in Aran, Looney and Liu et al. (2019). Each sample has been assigned to one of 37 main cell types and 157 subtypes.

se.aggregated <- sumCountsAcrossCells(sce, id=colLabels(sce))
hpc <- celldex::HumanPrimaryCellAtlasData()
anno.hca <- SingleR(se.aggregated, ref = hpc, labels = hpc$label.main, assay.type.test=\sum\)
anno.hca
DataFrame with 11 rows and 5 columns
                           scores     first.labels       tuning.scores
                         <matrix>      <character>         <DataFrame>
1  0.270340:0.754119:0.611547:...           B_cell 0.754119: 0.6997738
2  0.275371:0.598736:0.693408:... Pre-B_cell_CD34- 0.509690: 0.0634099
3  0.364176:0.613523:0.692287:...              CMP 0.594369: 0.3957973
4  0.282579:0.620968:0.575578:...          NK_cell 0.584395: 0.4455476
5  0.381883:0.558646:0.649733:...              MEP 0.361367: 0.3372588
6  0.291896:0.637409:0.575667:...          T_cells 0.773909: 0.7208437
7  0.272533:0.650300:0.600539:...          NK_cell 0.805271: 0.7248806
8  0.296865:0.637337:0.590623:...          T_cells 0.689381:-0.0530141
9  0.315724:0.599837:0.722784:... Pre-B_cell_CD34- 0.475323: 0.3332805
10 0.321822:0.686603:0.592921:...           B_cell 0.488044: 0.2851121
11 0.293917:0.635528:0.597594:... Pre-B_cell_CD34- 0.245206: 0.2048503
             labels    pruned.labels
        <character>      <character>
1            B_cell           B_cell
2          Monocyte         Monocyte
3               CMP              CMP
4           T_cells          T_cells
5        BM & Prog.       BM & Prog.
6           T_cells          T_cells
7           NK_cell          NK_cell
8           T_cells          T_cells
9  Pre-B_cell_CD34- Pre-B_cell_CD34-
10           B_cell           B_cell
11 Pre-B_cell_CD34- Pre-B_cell_CD34-
tab <- table(anno.hca$labels, colnames(se.aggregated))
# Adding a pseudo-count of 10 to avoid strong color jumps with just 1 cell.
pheatmap(log10(tab+10))

sce$cell_type<-recode(sce$label,
       "1" = "T_cells", 
       "2" = "Monocyte", 
       "3"="B_cell",
       "4"="MEP", 
       "5"="B_cell", 
       "6"="CMP", 
       "7"="T_cells",
      "8"="Monocyte",
      "9"="T_cells",
      "10"="Pro-B_cell_CD34+",
      "11"="NK_cell",
      "12"="B_cell")
#level_key <- anno.hca %>%
#  data.frame() %>%
#  rownames_to_column(\clu\) %>%
#  #select(clu, labels)
#  pull(labels)
level_key <- anno.hca$labels
names(level_key) <- row.names(anno.hca)
sce$cell_type <- recode(sce$label, !!!level_key)

We can now use the predicted cell types to color PCA, UMAP and tSNE.

plotPCA(sce, colour_by=\cell_type\, text_by=\cell_type\) + ggtitle(\PCA\)

plotUMAP(sce, colour_by=\cell_type\, text_by=\cell_type\) + ggtitle(\UMAP\)

plotTSNE(sce, colour_by=\cell_type\, text_by=\cell_type\) + ggtitle(\tSNE\)

We can also check expression of some marker genes.

CD3D and TRAC are used as marker genes for T-cells Szabo et al. 2019.

plotExpression(sce, features=c(\CD3D\), x=\label\, colour_by=\cell_type\)

plotExpression(sce, features=c(\TRAC\), x=\label\, colour_by=\cell_type\)

1.2.6 Extract T-cells

We will now extract T-cells and store in a new SCE object to use in pseudotime analysis.

Pull barcodes for T-cells

tcell.bc <- colData(sce) %>%
    data.frame() %>%
    group_by(cell_type) %>%
    dplyr::filter(cell_type == \T_cells\) %>%
    pull(Barcode)

table(colData(sce)$Barcode %in% tcell.bc)

FALSE  TRUE 
19478 20522 

Create a new SingleCellExperiment object for T-cells

tmpInd <- which(colData(sce)$Barcode %in% tcell.bc)
sce.tcell <- sce[,tmpInd]
saveRDS(sce.tcell,\../CourseMaterials/Robjects/sce.tcell.RDS\)
#rm(sce.tcell)

1.3 Setting up the data

In many situations, one is studying a process where cells change continuously. This includes, for example, many differentiation processes taking place during development: following a stimulus, cells will change from one cell-type to another. Ideally, we would like to monitor the expression levels of an individual cell over time. Unfortunately, such monitoring is not possible with scRNA-seq since the cell is lysed (destroyed) when the RNA is extracted.

Instead, we must sample at multiple time-points and obtain snapshots of the gene expression profiles. Since some of the cells will proceed faster along the differentiation than others, each snapshot may contain cells at varying points along the developmental progression. We use statistical methods to order the cells along one or more trajectories which represent the underlying developmental trajectories, this ordering is referred to as “pseudotime”.

A recent benchmarking paper by Saelens et al provides a detailed summary of the various computational methods for trajectory inference from single-cell transcriptomics. They discuss 45 tools and evaluate them across various aspects including accuracy, scalability, and usability. They provide dynverse, an open set of packages to benchmark, construct and interpret single-cell trajectories (currently they have a uniform interface for 60 methods). We load the SCE object we have generated previously. This object contains only the T-cells from 8 healthy donors. We will first prepare the data by identifying variable genes, integrating the data across donors and calculating principal components.

sce.tcell <- readRDS(\../CourseMaterials/Robjects/sce.tcell.RDS\)
sce.tcell
class: SingleCellExperiment 
dim: 20425 20522 
metadata(0):
assays(2): counts logcounts
rownames(20425): AL627309.1 AL669831.5 ... AC233755.1 AC240274.1
rowData names(11): ensembl_gene_id external_gene_name ... detected
  gene_sparsity
colnames: NULL
colData names(19): Sample Barcode ... label cell_type
reducedDimNames(4): MNN PCA UMAP TSNE
altExpNames(0):
dec.tcell <- modelGeneVar(sce.tcell, block=sce.tcell$Sample.Name)
top.tcell <- getTopHVGs(dec.tcell, n=5000)
set.seed(1010001)
merged.tcell <- fastMNN(sce.tcell, batch = sce.tcell$Sample.Name, subset.row = top.tcell)
reducedDim(sce.tcell, 'MNN') <- reducedDim(merged.tcell, 'corrected')
sce.tcell <- runPCA(sce.tcell, dimred=\MNN\)
plotPCA(sce.tcell, colour_by=\Sample.Name\)

1.3.1 Trajectory inference with destiny

Diffusion maps were introduced by Ronald Coifman and Stephane Lafon, and the underlying idea is to assume that the data are samples from a diffusion process. The method infers the low-dimensional manifold by estimating the eigenvalues and eigenvectors for the diffusion operator related to the data. Angerer et al have applied the diffusion maps concept to the analysis of single-cell RNA-seq data to create an R package called destiny.

For ease of computation, we will perform pseudotime analysis only on one sample, and we will downsample the object to 1000 cells. We will select the sample named MantonBM1.

# pull the barcodes for MantonBM1 sample & and downsample the set to 1000 genes 
vec.bc <- colData(sce.tcell) %>%
    data.frame() %>%
    filter(Sample.Name == \MantonBM1\) %>%
    group_by(Sample.Name) %>%
    sample_n(1000) %>%
    pull(Barcode)

Number of cells in the sample:

table(colData(sce.tcell)$Barcode %in% vec.bc)

FALSE  TRUE 
19522  1000 

Subset cells from the main SCE object:

tmpInd <- which(colData(sce.tcell)$Barcode %in% vec.bc)
sce.tcell.BM1 <- sce.tcell[,tmpInd]
sce.tcell.BM1
class: SingleCellExperiment 
dim: 20425 1000 
metadata(0):
assays(2): counts logcounts
rownames(20425): AL627309.1 AL669831.5 ... AC233755.1 AC240274.1
rowData names(11): ensembl_gene_id external_gene_name ... detected
  gene_sparsity
colnames: NULL
colData names(19): Sample Barcode ... label cell_type
reducedDimNames(4): MNN PCA UMAP TSNE
altExpNames(0):

Identify top 500 highly variable genes

dec.tcell.BM1 <- modelGeneVar(sce.tcell.BM1)
top.tcell.BM1 <- getTopHVGs(dec.tcell.BM1, n=500)

We will extract normalized counts for HVG to use in pseudotime alignment

tcell.BM1_counts <- logcounts(sce.tcell.BM1)
tcell.BM1_counts <- t(as.matrix(tcell.BM1_counts[top.tcell.BM1,]))
cellLabels <- sce.tcell.BM1$Barcode
rownames(tcell.BM1_counts) <- cellLabels
tcell.BM1_counts[1:4,1:4]
                        CCL5     NKG7   S100A4 KLRB1
AAACCTGAGCAGCGTA-13 3.733773 1.609114 2.838333     0
AAACCTGAGCGATATA-13 0.000000 0.000000 3.889855     0
AAACCTGGTAATCACC-13 0.000000 0.000000 3.576974     0
AAACCTGGTACAGTTC-13 3.337079 1.712109 3.630169     0

And finally, we can run pseudotime alignment with destiny

dm_tcell_BM1 <- DiffusionMap(tcell.BM1_counts,n_pcs = 50)

Plot diffusion component 1 vs diffusion component 2 (DC1 vs DC2).

tmp <- data.frame(DC1 = eigenvectors(dm_tcell_BM1)[, 1],
                  DC2 = eigenvectors(dm_tcell_BM1)[, 2])

ggplot(tmp, aes(x = DC1, y = DC2)) +
    geom_point() + 
    xlab(\Diffusion component 1\) + 
    ylab(\Diffusion component 2\) +
    theme_classic()

Stash diffusion components to SCE object

sce.tcell.BM1$pseudotime_destiny_1 <- eigenvectors(dm_tcell_BM1)[, 1]
sce.tcell.BM1$pseudotime_destiny_2 <- eigenvectors(dm_tcell_BM1)[, 2]

1.3.2 Find temporally expressed genes

After running destiny, an interesting next step may be to find genes that change their expression over the course of time We demonstrate one possible method for this type of analysis on the 500 most variable genes. We will regress each gene on the pseudotime variable we have generated, using a general additive model (GAM). This allows us to detect non-linear patterns in gene expression. We are going to use HVG we identified in the previous step, but this analysis can also be done using the whole transcriptome.

# Only look at the 500 most variable genes when identifying temporally expressesd genes.
# Identify the variable genes by ranking all genes by their variance.
# We will use the first diffusion components as a measure of pseudotime 
Y<-log2(counts(sce.tcell.BM1)+1)
colnames(Y)<-cellLabels
Y<-Y[top.tcell.BM1,]
# Fit GAM for each gene using pseudotime as independent variable.
t <- eigenvectors(dm_tcell_BM1)[, 1]
gam.pval <- apply(Y, 1, function(z){
  d <- data.frame(z=z, t=t)
  tmp <- gam(z ~ lo(t), data=d)
  p <- summary(tmp)[4][[1]][1,5]
  p
})

Select top 30 genes for visualization

# Identify genes with the most significant time-dependent model fit.
topgenes <- names(sort(gam.pval, decreasing = FALSE))[1:30]  

Visualize these genes in a heatmap

heatmapdata <- Y[topgenes,]
heatmapdata <- heatmapdata[,order(t, na.last = NA)]
t_ann<-as.data.frame(t)
colnames(t_ann)<-\pseudotime\
pheatmap(heatmapdata, cluster_rows = T, cluster_cols = F, color = plasma(200), show_colnames = F, annotation_col = t_ann)

Visualize how some of the temporally expressed genes change in time

Following individual genes is very helpful for identifying genes that play an important role in the differentiation process. We illustrate the procedure using the GZMA gene. We have added the pseudotime values computed with destiny to the colData slot of the SCE object. Having done that, the full plotting capabilities of the scater package can be used to investigate relationships between gene expression, cell populations and pseudotime.

plotExpression(sce.tcell.BM1,
           \GZMA\,
           x = \pseudotime_destiny_1\, 
               show_violin = TRUE,
               show_smooth = TRUE)

1.3.3 Pseudotime analysis for another HCA sample

# pull barcodes for MantonBM2 
vec.bc <- colData(sce.tcell) %>%
    data.frame() %>%
    filter(Sample.Name == "MantonBM2") %>%
    group_by(Sample.Name) %>%
    sample_n(1000) %>%
    pull(Barcode)
Error in colData(sce.tcell) : object 'sce.tcell' not found
LS0tCnRpdGxlOiAiQ1JVSyBDSSBTdW1tZXIgU2Nob29sIDIwMjEiCnN1YnRpdGxlOiAnUHNldWRvdGltZSBBbmFseXNpcycKYXV0aG9yOiAiWmV5bmVwIEthbGVuZGVyLUF0YWssIFN0ZXBoYW5lIEJhbGxlcmVhdSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgaHRtbF9ib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQoKIyBQc2V1ZG90aW1lIGFuYWx5c2lzIHsjcHNldWRvVGltZVRvcH0KCiMjIExlYXJuaW5nIG9iamVjdGl2ZXMKCiogCiogCiogCgpgYGB7ciBsaWJyYXJ5X3BzZXVkb3RpbWV9CmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoc2NyYW4pCmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KGJhdGNoZWxvcikKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShTaW5nbGVSKQpsaWJyYXJ5KGRlc3RpbnkpCmxpYnJhcnkoZ2FtKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkobXNpZ2RicikKbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpCiNsaWJyYXJ5KGNlbGxBbGlnbikgIyBodHRwczovL2dpdGh1Yi5jb20vc2hlbm9yckxhYi9jZWxsQWxpZ24KI2xpYnJhcnkoQ2Fpcm8pCmBgYAoKIyMgRXh0cmFjdCBULWNlbGxzIGZyb20gSENBIEFCTU1DIERhdGFzZXQgeyNwc2V1ZG9UaW1lRXh0cmFjdFRDZWxsfQoKSW4gdGhpcyBzZWN0aW9uLCB3ZSBhcmUgc3RhcnRpbmcgb3VyIGFuYWx5c2lzIHdpdGggbm9ybWFsaXplZCBbSENBXShodHRwczovL3ByZXZpZXcuZGF0YS5odW1hbmNlbGxhdGxhcy5vcmcpIGRhdGEgYW5kIHBlcmZvcm0gaW50ZWdyYXRpb24sIGNsdXN0ZXJpbmcgYW5kIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbi4gT3VyIGFpbSBpcyB0byBleHRyYWN0IFQtY2VsbHMgZnJvbSB0aGlzIGRhdGFzZXQgYW5kIHByb2NlZWQgd2l0aCBwc2V1ZG90aW1lIGFuYWx5c2lzIGluIHRoZSBuZXh0IHNlY3Rpb24uIAoKV2UgYXJlIGdvaW5nIHRvIHdvcmsgd2l0aCBIQ0EgZGF0YS4gVGhpcyBkYXRhIHNldCBoYXMgYmVlbiBwcmUtcHJvY2Vzc2VkIGFuZCBub3JtYWxpemVkIGJlZm9yZS4KCgpgYGB7ciBsb2FkX3BzZXVkb3RpbWUxfQpzY2UgPC0gcmVhZFJEUygiLi4vQ291cnNlTWF0ZXJpYWxzL1JvYmplY3RzL2hjYV9zY2VfbnpfcG9zdERlY29udl81a0NlbGxQZXJTcGwuUmRzIikKYGBgCgpXZSB1c2Ugc3ltYm9scyBpbiBwbGFjZSBvZiBFTlNFTUJMIElEcyBmb3IgZWFzaWVyIGludGVycHJldGF0aW9uIGxhdGVyLgoKYGBge3J9CnJvd25hbWVzKHNjZSkgPC0gdW5pcXVpZnlGZWF0dXJlTmFtZXMocm93RGF0YShzY2UpJGVuc2VtYmxfZ2VuZV9pZCwgbmFtZXMgPSByb3dEYXRhKHNjZSkkU3ltYm9sKQpgYGAKCiMjIyBWYXJpYW5jZSBtb2RlbGluZwoKV2UgYmxvY2sgb24gdGhlIGRvbm9yIG9mIG9yaWdpbiB0byBtaXRpZ2F0ZSBiYXRjaCBlZmZlY3RzIGR1cmluZyBoaWdobHkgdmFyaWFibGUgZ2VuZSAoSFZHKSBzZWxlY3Rpb24uCldlIHNlbGVjdCBhIGxhcmdlciBudW1iZXIgb2YgSFZHcyB0byBjYXB0dXJlIGFueSBiYXRjaC1zcGVjaWZpYyB2YXJpYXRpb24gdGhhdCBtaWdodCBiZSBwcmVzZW50LgoKYGBge3IgdmFyTW9kZWxfcHNldWRvdGltZTF9CmRlYy5oY2EgPC0gbW9kZWxHZW5lVmFyKHNjZSwgYmxvY2s9c2NlJFNhbXBsZS5OYW1lKQp0b3AuaGNhIDwtIGdldFRvcEhWR3MoZGVjLmhjYSwgbj01MDAwKQpgYGAKCiMjIyBEYXRhIGludGVncmF0aW9uCgpUaGUgYGJhdGNoZWxvcmAgcGFja2FnZSBwcm92aWRlcyBhbiBpbXBsZW1lbnRhdGlvbiBvZiB0aGUgTXV0dWFsIE5lYXJlc3QgTmVpZ2hib3VycyAoTU5OKSBhcHByb2FjaCB2aWEgdGhlIGZhc3RNTk4oKSBmdW5jdGlvbi4gV2UgYXBwbHkgaXQgdG8gb3VyIEhDQSBkYXRhIHRvIHJlbW92ZSB0aGUgZG9ub3Igc3BlY2lmaWMgZWZmZWN0cyBhY3Jvc3MgdGhlIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyBpbiBgdG9wLmhjYWAuIFRvIHJlZHVjZSBjb21wdXRhdGlvbmFsIHdvcmsgYW5kIHRlY2huaWNhbCBub2lzZSwgYWxsIGNlbGxzIGluIGFsbCBzYW1wbGVzIGFyZSBwcm9qZWN0ZWQgaW50byB0aGUgbG93LWRpbWVuc2lvbmFsIHNwYWNlIGRlZmluZWQgYnkgdGhlIHRvcCBkIHByaW5jaXBhbCBjb21wb25lbnRzLiBJZGVudGlmaWNhdGlvbiBvZiBNTk5zIGFuZCBjYWxjdWxhdGlvbiBvZiBjb3JyZWN0aW9uIHZlY3RvcnMgYXJlIHRoZW4gcGVyZm9ybWVkIGluIHRoaXMgbG93LWRpbWVuc2lvbmFsIHNwYWNlLgpUaGUgY29ycmVjdGVkIG1hdHJpeCBpbiB0aGUgcmVkdWNlZERpbXMoKSBjb250YWlucyB0aGUgbG93LWRpbWVuc2lvbmFsIGNvcnJlY3RlZCBjb29yZGluYXRlcyBmb3IgYWxsIGNlbGxzLCB3aGljaCB3ZSB3aWxsIHVzZSBpbiBwbGFjZSBvZiB0aGUgUENzIGluIG91ciBkb3duc3RyZWFtIGFuYWx5c2VzLiBXZSBzdG9yZSBpdCBpbiAnTU5OJyBzbG90IGluIHRoZSBtYWluIHNjZSBvYmplY3QuIAoKYGBge3IgZmFzdE1OTl9wc2V1ZG90aW1lMX0Kc2V0LnNlZWQoMTAxMDAwMSkKbWVyZ2VkLmhjYSA8LSBmYXN0TU5OKHNjZSwKCQkgICAgICBiYXRjaCA9IHNjZSRTYW1wbGUuTmFtZSwKCQkgICAgICBzdWJzZXQucm93ID0gdG9wLmhjYSkKcmVkdWNlZERpbShzY2UsICdNTk4nKSA8LSByZWR1Y2VkRGltKG1lcmdlZC5oY2EsICdjb3JyZWN0ZWQnKQpgYGAKCiMjIyBEaW1lbnNpb25hbGl0eSBSZWR1Y3Rpb24KCldlIGNsdXN0ZXIgb24gdGhlIGxvdy1kaW1lbnNpb25hbCBjb3JyZWN0ZWQgY29vcmRpbmF0ZXMgdG8gb2J0YWluIGEgcGFydGl0aW9uaW5nIG9mIHRoZSBjZWxscyB0aGF0IHNlcnZlcyBhcyBhIHByb3h5IGZvciB0aGUgcG9wdWxhdGlvbiBzdHJ1Y3R1cmUuIElmIHRoZSBiYXRjaCBlZmZlY3QgaXMgc3VjY2Vzc2Z1bGx5IGNvcnJlY3RlZCwgY2x1c3RlcnMgY29ycmVzcG9uZGluZyB0byBzaGFyZWQgY2VsbCB0eXBlcyBvciBzdGF0ZXMgc2hvdWxkIGNvbnRhaW4gY2VsbHMgZnJvbSBtdWx0aXBsZSBzYW1wbGVzLiBXZSBzZWUgdGhhdCBhbGwgY2x1c3RlcnMgY29udGFpbiBjb250cmlidXRpb25zIGZyb20gZWFjaCBzYW1wbGUgYWZ0ZXIgY29ycmVjdGlvbi4KCmBgYHtyIGRpbVJlZF9jb21wX3BzZXVkb3RpbWUxfQpzZXQuc2VlZCgwMTAxMDEwMCkKc2NlIDwtIHJ1blBDQShzY2UsIGRpbXJlZD0iTU5OIikKc2NlIDwtIHJ1blVNQVAoc2NlLCBkaW1yZWQ9Ik1OTiIpCnNjZSA8LSBydW5UU05FKHNjZSwgZGltcmVkPSJNTk4iKQpgYGAKCmBgYHtyIGRpbVJlZF9wbG90X3BzZXVkb3RpbWUxfQpwbG90UENBKHNjZSwgY29sb3VyX2J5PSJTYW1wbGUuTmFtZSIpICsgZ2d0aXRsZSgiUENBIikKcGxvdFRTTkUoc2NlLCBjb2xvdXJfYnk9IlNhbXBsZS5OYW1lIikgKyBnZ3RpdGxlKCJ0U05FIikKcGxvdFVNQVAoc2NlLCBjb2xvdXJfYnk9IlNhbXBsZS5OYW1lIikgKyBnZ3RpdGxlKCJVTUFQIikKYGBgCgojIyMgQ2x1c3RlcmluZwoKR3JhcGgtYmFzZWQgY2x1c3RlcmluZyBnZW5lcmF0ZXMgYW4gZXhjZXNzaXZlbHkgbGFyZ2UgaW50ZXJtZWRpYXRlIGdyYXBoIHNvIHdlIHdpbGwgaW5zdGVhZCB1c2UgYSB0d28tc3RlcCBhcHByb2FjaCB3aXRoIGstbWVhbnMuIFdlIGdlbmVyYXRlIDEwMDAgc21hbGwgY2x1c3RlcnMgdGhhdCBhcmUgc3Vic2VxdWVudGx5IGFnZ3JlZ2F0ZWQgaW50byBtb3JlIGludGVycHJldGFibGUgZ3JvdXBzIHdpdGggYSBncmFwaC1iYXNlZCBtZXRob2QuCgpgYGB7ciBjbHVzdGVyaW5nX2NvbXBfcHNldWRvdGltZTF9CnNldC5zZWVkKDEwMDApCmNsdXN0LmhjYSA8LSBjbHVzdGVyU05OR3JhcGgoc2NlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZS5kaW1yZWQ9Ik1OTiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlLmttZWFucz1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGttZWFucy5jZW50ZXJzPTEwMDApCgpjb2xMYWJlbHMoc2NlKSA8LSBmYWN0b3IoY2x1c3QuaGNhKQp0YWJsZShjb2xMYWJlbHMoc2NlKSkKYGBgCgpgYGB7ciBjbHVzdGVyaW5nX3Bsb3RfcHNldWRvdGltZTF9CnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9ImxhYmVsIikgKyBnZ3RpdGxlKCJQQ0EiKQpwbG90VU1BUChzY2UsIGNvbG91cl9ieT0ibGFiZWwiKSArIGdndGl0bGUoIlVNQVAiKQpwbG90VFNORShzY2UsIGNvbG91cl9ieT0ibGFiZWwiKSArIGdndGl0bGUoInRTTkUiKQpgYGAKCiMjIyBDZWxsIHR5cGUgY2xhc3NpZmljYXRpb24geyNjZWxsVHlwZUFubm90YXRpb259CgpXZSBwZXJmb3JtIGF1dG9tYXRlZCBjZWxsIHR5cGUgY2xhc3NpZmljYXRpb24gdXNpbmcgYSByZWZlcmVuY2UgZGF0YXNldCB0byBhbm5vdGF0ZSBlYWNoIGNsdXN0ZXIgYmFzZWQgb24gaXRzIHBzZXVkby1idWxrIHByb2ZpbGUuIFRoaXMgaXMgZm9yIGEgcXVpY2sgYXNzaWdubWVudCBvZiBjbHVzdGVyIGlkZW50aXR5LiBXZSBhcmUgZ29pbmcgdG8gdXNlIEh1bWFuIFByaW1hcnkgQ2VsbCBBdGxhcyAoSFBDQSkgZGF0YSBmb3IgdGhhdC4gYEh1bWFuUHJpbWFyeUNlbGxBdGxhc0RhdGFgIGZ1bmN0aW9uIHByb3ZpZGVzIG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIDcxMyBtaWNyb2FycmF5IHNhbXBsZXMgZnJvbSBIUENBIChbTWFiYm90dCBldCBhbC4sIDIwMTNdKGh0dHBzOi8vYm1jZ2Vub21pY3MuYmlvbWVkY2VudHJhbC5jb20vYXJ0aWNsZXMvMTAuMTE4Ni8xNDcxLTIxNjQtMTQtNjMyKSkuClRoZXNlIDcxMyBzYW1wbGVzIHdlcmUgcHJvY2Vzc2VkIGFuZCBub3JtYWxpemVkIGFzIGRlc2NyaWJlZCBpbiBbQXJhbiwgTG9vbmV5IGFuZCBMaXUgZXQgYWwuICgyMDE5KV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE1OTAtMDE4LTAyNzYteSkuCkVhY2ggc2FtcGxlIGhhcyBiZWVuIGFzc2lnbmVkIHRvIG9uZSBvZiAzNyBtYWluIGNlbGwgdHlwZXMgYW5kIDE1NyBzdWJ0eXBlcy4KCmBgYHtyIGNlbGxUeXBlX2NvbXBfUHNldWRvdGltZTF9CnNlLmFnZ3JlZ2F0ZWQgPC0gc3VtQ291bnRzQWNyb3NzQ2VsbHMoc2NlLCBpZD1jb2xMYWJlbHMoc2NlKSkKaHBjIDwtIGNlbGxkZXg6Okh1bWFuUHJpbWFyeUNlbGxBdGxhc0RhdGEoKQphbm5vLmhjYSA8LSBTaW5nbGVSKHNlLmFnZ3JlZ2F0ZWQsIHJlZiA9IGhwYywgbGFiZWxzID0gaHBjJGxhYmVsLm1haW4sIGFzc2F5LnR5cGUudGVzdD0ic3VtIikKYW5uby5oY2EKYGBgCgoKYGBge3J9CnRhYiA8LSB0YWJsZShhbm5vLmhjYSRsYWJlbHMsIGNvbG5hbWVzKHNlLmFnZ3JlZ2F0ZWQpKQojIEFkZGluZyBhIHBzZXVkby1jb3VudCBvZiAxMCB0byBhdm9pZCBzdHJvbmcgY29sb3IganVtcHMgd2l0aCBqdXN0IDEgY2VsbC4KcGhlYXRtYXAobG9nMTAodGFiKzEwKSkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0Kc2NlJGNlbGxfdHlwZTwtcmVjb2RlKHNjZSRsYWJlbCwKICAgICAgICIxIiA9ICJUX2NlbGxzIiwgCiAgICAgICAiMiIgPSAiTW9ub2N5dGUiLCAKICAgICAgICIzIj0iQl9jZWxsIiwKICAgICAgICI0Ij0iTUVQIiwgCiAgICAgICAiNSI9IkJfY2VsbCIsIAogICAgICAgIjYiPSJDTVAiLCAKICAgICAgICI3Ij0iVF9jZWxscyIsCiAgICAgICI4Ij0iTW9ub2N5dGUiLAogICAgICAiOSI9IlRfY2VsbHMiLAogICAgICAiMTAiPSJQcm8tQl9jZWxsX0NEMzQrIiwKICAgICAgIjExIj0iTktfY2VsbCIsCiAgICAgICIxMiI9IkJfY2VsbCIpCmBgYAoKYGBge3J9CiNsZXZlbF9rZXkgPC0gYW5uby5oY2EgJT4lCiMgIGRhdGEuZnJhbWUoKSAlPiUKIyAgcm93bmFtZXNfdG9fY29sdW1uKCJjbHUiKSAlPiUKIyAgI3NlbGVjdChjbHUsIGxhYmVscykKIyAgcHVsbChsYWJlbHMpCmxldmVsX2tleSA8LSBhbm5vLmhjYSRsYWJlbHMKbmFtZXMobGV2ZWxfa2V5KSA8LSByb3cubmFtZXMoYW5uby5oY2EpCnNjZSRjZWxsX3R5cGUgPC0gcmVjb2RlKHNjZSRsYWJlbCwgISEhbGV2ZWxfa2V5KQpgYGAKCldlIGNhbiBub3cgdXNlIHRoZSBwcmVkaWN0ZWQgY2VsbCB0eXBlcyB0byBjb2xvciBQQ0EsIFVNQVAgYW5kIHRTTkUuIAoKYGBge3J9CnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9ImNlbGxfdHlwZSIsIHRleHRfYnk9ImNlbGxfdHlwZSIpICsgZ2d0aXRsZSgiUENBIikKcGxvdFVNQVAoc2NlLCBjb2xvdXJfYnk9ImNlbGxfdHlwZSIsIHRleHRfYnk9ImNlbGxfdHlwZSIpICsgZ2d0aXRsZSgiVU1BUCIpCnBsb3RUU05FKHNjZSwgY29sb3VyX2J5PSJjZWxsX3R5cGUiLCB0ZXh0X2J5PSJjZWxsX3R5cGUiKSArIGdndGl0bGUoInRTTkUiKQpgYGAKCldlIGNhbiBhbHNvIGNoZWNrIGV4cHJlc3Npb24gb2Ygc29tZSBtYXJrZXIgZ2VuZXMuIAoKQ0QzRCBhbmQgVFJBQyBhcmUgdXNlZCBhcyBtYXJrZXIgZ2VuZXMgZm9yIFQtY2VsbHMgW1N6YWJvIGV0IGFsLiAyMDE5XShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTQ2Ny0wMTktMTI0NjQtMykuIAoKYGBge3J9CnBsb3RFeHByZXNzaW9uKHNjZSwgZmVhdHVyZXM9YygiQ0QzRCIpLCB4PSJsYWJlbCIsIGNvbG91cl9ieT0iY2VsbF90eXBlIikKYGBgCgpgYGB7cn0KcGxvdEV4cHJlc3Npb24oc2NlLCBmZWF0dXJlcz1jKCJUUkFDIiksIHg9ImxhYmVsIiwgY29sb3VyX2J5PSJjZWxsX3R5cGUiKQpgYGAKCiMjIyBFeHRyYWN0IFQtY2VsbHMKCldlIHdpbGwgbm93IGV4dHJhY3QgVC1jZWxscyBhbmQgc3RvcmUgaW4gYSBuZXcgU0NFIG9iamVjdCB0byB1c2UgaW4gcHNldWRvdGltZSBhbmFseXNpcy4gCgpQdWxsIGJhcmNvZGVzIGZvciBULWNlbGxzCgpgYGB7cn0KdGNlbGwuYmMgPC0gY29sRGF0YShzY2UpICU+JQogICAgZGF0YS5mcmFtZSgpICU+JQogICAgZ3JvdXBfYnkoY2VsbF90eXBlKSAlPiUKICAgIGRwbHlyOjpmaWx0ZXIoY2VsbF90eXBlID09ICJUX2NlbGxzIikgJT4lCiAgICBwdWxsKEJhcmNvZGUpCgp0YWJsZShjb2xEYXRhKHNjZSkkQmFyY29kZSAlaW4lIHRjZWxsLmJjKQpgYGAKCkNyZWF0ZSBhIG5ldyBTaW5nbGVDZWxsRXhwZXJpbWVudCBvYmplY3QgZm9yIFQtY2VsbHMgCgpgYGB7cn0KdG1wSW5kIDwtIHdoaWNoKGNvbERhdGEoc2NlKSRCYXJjb2RlICVpbiUgdGNlbGwuYmMpCnNjZS50Y2VsbCA8LSBzY2VbLHRtcEluZF0KYGBgCgpgYGB7cn0Kc2F2ZVJEUyhzY2UudGNlbGwsIi4uL0NvdXJzZU1hdGVyaWFscy9Sb2JqZWN0cy9zY2UudGNlbGwuUkRTIikKI3JtKHNjZS50Y2VsbCkKYGBgCgojIyBTZXR0aW5nIHVwIHRoZSBkYXRhIHsjcHNldWRvVGltZVNldFVwfQoKSW4gbWFueSBzaXR1YXRpb25zLCBvbmUgaXMgc3R1ZHlpbmcgYSBwcm9jZXNzIHdoZXJlIGNlbGxzIGNoYW5nZSBjb250aW51b3VzbHkuIFRoaXMgaW5jbHVkZXMsIGZvciBleGFtcGxlLCBtYW55IGRpZmZlcmVudGlhdGlvbiBwcm9jZXNzZXMgdGFraW5nIHBsYWNlIGR1cmluZyBkZXZlbG9wbWVudDogZm9sbG93aW5nIGEgc3RpbXVsdXMsIGNlbGxzIHdpbGwgY2hhbmdlIGZyb20gb25lIGNlbGwtdHlwZSB0byBhbm90aGVyLiBJZGVhbGx5LCB3ZSB3b3VsZCBsaWtlIHRvIG1vbml0b3IgdGhlIGV4cHJlc3Npb24gbGV2ZWxzIG9mIGFuIGluZGl2aWR1YWwgY2VsbCBvdmVyIHRpbWUuIFVuZm9ydHVuYXRlbHksIHN1Y2ggbW9uaXRvcmluZyBpcyBub3QgcG9zc2libGUgd2l0aCBzY1JOQS1zZXEgc2luY2UgdGhlIGNlbGwgaXMgbHlzZWQgKGRlc3Ryb3llZCkgd2hlbiB0aGUgUk5BIGlzIGV4dHJhY3RlZC4KCkluc3RlYWQsIHdlIG11c3Qgc2FtcGxlIGF0IG11bHRpcGxlIHRpbWUtcG9pbnRzIGFuZCBvYnRhaW4gc25hcHNob3RzIG9mIHRoZSBnZW5lIGV4cHJlc3Npb24gcHJvZmlsZXMuIFNpbmNlIHNvbWUgb2YgdGhlIGNlbGxzIHdpbGwgcHJvY2VlZCBmYXN0ZXIgYWxvbmcgdGhlIGRpZmZlcmVudGlhdGlvbiB0aGFuIG90aGVycywgZWFjaCBzbmFwc2hvdCBtYXkgY29udGFpbiBjZWxscyBhdCB2YXJ5aW5nIHBvaW50cyBhbG9uZyB0aGUgZGV2ZWxvcG1lbnRhbCBwcm9ncmVzc2lvbi4gV2UgdXNlIHN0YXRpc3RpY2FsIG1ldGhvZHMgdG8gb3JkZXIgdGhlIGNlbGxzIGFsb25nIG9uZSBvciBtb3JlIHRyYWplY3RvcmllcyB3aGljaCByZXByZXNlbnQgdGhlIHVuZGVybHlpbmcgZGV2ZWxvcG1lbnRhbCB0cmFqZWN0b3JpZXMsIHRoaXMgb3JkZXJpbmcgaXMgcmVmZXJyZWQgdG8gYXMg4oCccHNldWRvdGltZeKAnS4KCkEgcmVjZW50IGJlbmNobWFya2luZyBwYXBlciBieSBbU2FlbGVucyBldCBhbF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTg3LTAxOS0wMDcxLTkpIHByb3ZpZGVzIGEgZGV0YWlsZWQgc3VtbWFyeSBvZiB0aGUgdmFyaW91cyBjb21wdXRhdGlvbmFsIG1ldGhvZHMgZm9yIHRyYWplY3RvcnkgaW5mZXJlbmNlIGZyb20gc2luZ2xlLWNlbGwgdHJhbnNjcmlwdG9taWNzLiBUaGV5IGRpc2N1c3MgNDUgdG9vbHMgYW5kIGV2YWx1YXRlIHRoZW0gYWNyb3NzIHZhcmlvdXMgYXNwZWN0cyBpbmNsdWRpbmcgYWNjdXJhY3ksIHNjYWxhYmlsaXR5LCBhbmQgdXNhYmlsaXR5LiBUaGV5IHByb3ZpZGUgW2R5bnZlcnNlXShodHRwczovL2R5bnZlcnNlLm9yZyksIGFuIG9wZW4gc2V0IG9mIHBhY2thZ2VzIHRvIGJlbmNobWFyaywgY29uc3RydWN0IGFuZCBpbnRlcnByZXQgc2luZ2xlLWNlbGwgdHJhamVjdG9yaWVzIChjdXJyZW50bHkgdGhleSBoYXZlIGEgdW5pZm9ybSBpbnRlcmZhY2UgZm9yIDYwIG1ldGhvZHMpLiAKV2UgbG9hZCB0aGUgU0NFIG9iamVjdCB3ZSBoYXZlIGdlbmVyYXRlZCBwcmV2aW91c2x5LiBUaGlzIG9iamVjdCBjb250YWlucyBvbmx5IHRoZSBULWNlbGxzIGZyb20gOCBoZWFsdGh5IGRvbm9ycy4gV2Ugd2lsbCBmaXJzdCBwcmVwYXJlIHRoZSBkYXRhIGJ5IGlkZW50aWZ5aW5nIHZhcmlhYmxlIGdlbmVzLCBpbnRlZ3JhdGluZyB0aGUgZGF0YSBhY3Jvc3MgZG9ub3JzIGFuZCBjYWxjdWxhdGluZyBwcmluY2lwYWwgY29tcG9uZW50cy4gCgpgYGB7ciBsb2FkX3BzZXVkb3RpbWUyfQpzY2UudGNlbGwgPC0gcmVhZFJEUygiLi4vQ291cnNlTWF0ZXJpYWxzL1JvYmplY3RzL3NjZS50Y2VsbC5SRFMiKQpzY2UudGNlbGwKYGBgCgpgYGB7ciB2YXJNb2RlbF9wc2V1ZG90aW1lMn0KZGVjLnRjZWxsIDwtIG1vZGVsR2VuZVZhcihzY2UudGNlbGwsIGJsb2NrPXNjZS50Y2VsbCRTYW1wbGUuTmFtZSkKdG9wLnRjZWxsIDwtIGdldFRvcEhWR3MoZGVjLnRjZWxsLCBuPTUwMDApCmBgYAoKYGBge3IgZmFzdE1OTl9wc2V1ZG90aW1lMn0Kc2V0LnNlZWQoMTAxMDAwMSkKbWVyZ2VkLnRjZWxsIDwtIGZhc3RNTk4oc2NlLnRjZWxsLCBiYXRjaCA9IHNjZS50Y2VsbCRTYW1wbGUuTmFtZSwgc3Vic2V0LnJvdyA9IHRvcC50Y2VsbCkKcmVkdWNlZERpbShzY2UudGNlbGwsICdNTk4nKSA8LSByZWR1Y2VkRGltKG1lcmdlZC50Y2VsbCwgJ2NvcnJlY3RlZCcpCmBgYAoKYGBge3IgZGltUmVkX2NvbXBfcHNldWRvdGltZTJ9CnNjZS50Y2VsbCA8LSBydW5QQ0Eoc2NlLnRjZWxsLCBkaW1yZWQ9Ik1OTiIpCmBgYAoKYGBge3IgZGltUmVkX3Bsb3RfcHNldWRvdGltZTJ9CnBsb3RQQ0Eoc2NlLnRjZWxsLCBjb2xvdXJfYnk9IlNhbXBsZS5OYW1lIikKYGBgCgojIyMgVHJhamVjdG9yeSBpbmZlcmVuY2Ugd2l0aCBkZXN0aW55CgpbRGlmZnVzaW9uIG1hcHNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RpZmZ1c2lvbl9tYXApIHdlcmUgaW50cm9kdWNlZCBieSBbUm9uYWxkIENvaWZtYW4gYW5kIFN0ZXBoYW5lIExhZm9uXShodHRwOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzEwNjM1MjAzMDYwMDA1NDYpLCBhbmQgdGhlIHVuZGVybHlpbmcgaWRlYSBpcyB0byBhc3N1bWUgdGhhdCB0aGUgZGF0YSBhcmUgc2FtcGxlcyBmcm9tIGEgZGlmZnVzaW9uIHByb2Nlc3MuIFRoZSBtZXRob2QgaW5mZXJzIHRoZSBsb3ctZGltZW5zaW9uYWwgbWFuaWZvbGQgYnkgZXN0aW1hdGluZyB0aGUgZWlnZW52YWx1ZXMgYW5kIGVpZ2VudmVjdG9ycyBmb3IgdGhlIGRpZmZ1c2lvbiBvcGVyYXRvciByZWxhdGVkIHRvIHRoZSBkYXRhLiBbQW5nZXJlciBldCBhbF0oaHR0cHM6Ly9hY2FkZW1pYy5vdXAuY29tL2Jpb2luZm9ybWF0aWNzL2FydGljbGUvMzIvOC8xMjQxLzE3NDQxNDMpIGhhdmUgYXBwbGllZCB0aGUgZGlmZnVzaW9uIG1hcHMgY29uY2VwdCB0byB0aGUgYW5hbHlzaXMgb2Ygc2luZ2xlLWNlbGwgUk5BLXNlcSBkYXRhIHRvIGNyZWF0ZSBhbiBSIHBhY2thZ2UgY2FsbGVkIGBkZXN0aW55LmAKCkZvciBlYXNlIG9mIGNvbXB1dGF0aW9uLCB3ZSB3aWxsIHBlcmZvcm0gcHNldWRvdGltZSBhbmFseXNpcyBvbmx5IG9uIG9uZSBzYW1wbGUsIGFuZCB3ZSB3aWxsIGRvd25zYW1wbGUgdGhlIG9iamVjdCB0byAxMDAwIGNlbGxzLiBXZSB3aWxsIHNlbGVjdCB0aGUgc2FtcGxlIG5hbWVkIGBNYW50b25CTTFgLiAKCmBgYHtyIE1hbnRvbkJNMV9kb3duU2FtcGxlX3BzZXVkb3RpbWUyfQojIHB1bGwgdGhlIGJhcmNvZGVzIGZvciBNYW50b25CTTEgc2FtcGxlICYgYW5kIGRvd25zYW1wbGUgdGhlIHNldCB0byAxMDAwIGdlbmVzIAp2ZWMuYmMgPC0gY29sRGF0YShzY2UudGNlbGwpICU+JQogICAgZGF0YS5mcmFtZSgpICU+JQogICAgZmlsdGVyKFNhbXBsZS5OYW1lID09ICJNYW50b25CTTEiKSAlPiUKICAgIGdyb3VwX2J5KFNhbXBsZS5OYW1lKSAlPiUKICAgIHNhbXBsZV9uKDEwMDApICU+JQogICAgcHVsbChCYXJjb2RlKQpgYGAKCk51bWJlciBvZiBjZWxscyBpbiB0aGUgc2FtcGxlOgoKYGBge3IgTWFudG9uQk0xX2NlbGxOdW1iZXJfcHNldWRvdGltZTJ9CnRhYmxlKGNvbERhdGEoc2NlLnRjZWxsKSRCYXJjb2RlICVpbiUgdmVjLmJjKQpgYGAKClN1YnNldCBjZWxscyBmcm9tIHRoZSBtYWluIFNDRSBvYmplY3Q6CgpgYGB7ciBNYW50b25CTTFfc2NlX3BzZXVkb3RpbWUyfQp0bXBJbmQgPC0gd2hpY2goY29sRGF0YShzY2UudGNlbGwpJEJhcmNvZGUgJWluJSB2ZWMuYmMpCnNjZS50Y2VsbC5CTTEgPC0gc2NlLnRjZWxsWyx0bXBJbmRdCnNjZS50Y2VsbC5CTTEKYGBgCgpJZGVudGlmeSB0b3AgNTAwIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyAKCmBgYHtyIE1hbnRvbkJNMV92YXJNb2RlbF9wc2V1ZG90aW1lMn0KZGVjLnRjZWxsLkJNMSA8LSBtb2RlbEdlbmVWYXIoc2NlLnRjZWxsLkJNMSkKdG9wLnRjZWxsLkJNMSA8LSBnZXRUb3BIVkdzKGRlYy50Y2VsbC5CTTEsIG49NTAwKQpgYGAKCldlIHdpbGwgZXh0cmFjdCBub3JtYWxpemVkIGNvdW50cyBmb3IgSFZHIHRvIHVzZSBpbiBwc2V1ZG90aW1lIGFsaWdubWVudAoKYGBge3IgTWFudG9uQk0xX2dldENvdW50c19wc2V1ZG90aW1lMn0KdGNlbGwuQk0xX2NvdW50cyA8LSBsb2djb3VudHMoc2NlLnRjZWxsLkJNMSkKdGNlbGwuQk0xX2NvdW50cyA8LSB0KGFzLm1hdHJpeCh0Y2VsbC5CTTFfY291bnRzW3RvcC50Y2VsbC5CTTEsXSkpCmNlbGxMYWJlbHMgPC0gc2NlLnRjZWxsLkJNMSRCYXJjb2RlCnJvd25hbWVzKHRjZWxsLkJNMV9jb3VudHMpIDwtIGNlbGxMYWJlbHMKYGBgCgpgYGB7ciBNYW50b25CTTFfc2hvd0NvdW50c19wc2V1ZG90aW1lMn0KdGNlbGwuQk0xX2NvdW50c1sxOjQsMTo0XQpgYGAKCkFuZCBmaW5hbGx5LCB3ZSBjYW4gcnVuIHBzZXVkb3RpbWUgYWxpZ25tZW50IHdpdGggZGVzdGlueSAKCmBgYHtyIE1hbnRvbkJNMV9kaWZmdXNNYXBfY29tcF9wc2V1ZG90aW1lMn0KZG1fdGNlbGxfQk0xIDwtIERpZmZ1c2lvbk1hcCh0Y2VsbC5CTTFfY291bnRzLG5fcGNzID0gNTApCmBgYAoKUGxvdCBkaWZmdXNpb24gY29tcG9uZW50IDEgdnMgZGlmZnVzaW9uIGNvbXBvbmVudCAyIChEQzEgdnMgREMyKS4gCgpgYGB7ciBNYW50b25CTTFfZGlmZnVzTWFwX3Bsb3RfcHNldWRvdGltZTJ9CnRtcCA8LSBkYXRhLmZyYW1lKERDMSA9IGVpZ2VudmVjdG9ycyhkbV90Y2VsbF9CTTEpWywgMV0sCiAgICAgICAgICAgICAgICAgIERDMiA9IGVpZ2VudmVjdG9ycyhkbV90Y2VsbF9CTTEpWywgMl0pCgpnZ3Bsb3QodG1wLCBhZXMoeCA9IERDMSwgeSA9IERDMikpICsKICAgIGdlb21fcG9pbnQoKSArIAogICAgeGxhYigiRGlmZnVzaW9uIGNvbXBvbmVudCAxIikgKyAKICAgIHlsYWIoIkRpZmZ1c2lvbiBjb21wb25lbnQgMiIpICsKICAgIHRoZW1lX2NsYXNzaWMoKQpgYGAKClN0YXNoIGRpZmZ1c2lvbiBjb21wb25lbnRzIHRvIFNDRSBvYmplY3QKCmBgYHtyIE1hbnRvbkJNMV9kaWZmdXNNYXBfc3RvcmVfcHNldWRvdGltZTJ9CnNjZS50Y2VsbC5CTTEkcHNldWRvdGltZV9kZXN0aW55XzEgPC0gZWlnZW52ZWN0b3JzKGRtX3RjZWxsX0JNMSlbLCAxXQpzY2UudGNlbGwuQk0xJHBzZXVkb3RpbWVfZGVzdGlueV8yIDwtIGVpZ2VudmVjdG9ycyhkbV90Y2VsbF9CTTEpWywgMl0KYGBgCgojIyMgRmluZCB0ZW1wb3JhbGx5IGV4cHJlc3NlZCBnZW5lcwoKQWZ0ZXIgcnVubmluZyBkZXN0aW55LCBhbiBpbnRlcmVzdGluZyBuZXh0IHN0ZXAgbWF5IGJlIHRvIGZpbmQgZ2VuZXMgdGhhdCBjaGFuZ2UgdGhlaXIgZXhwcmVzc2lvbiBvdmVyIHRoZSBjb3Vyc2Ugb2YgdGltZSBXZSBkZW1vbnN0cmF0ZSBvbmUgcG9zc2libGUgbWV0aG9kIGZvciB0aGlzIHR5cGUgb2YgYW5hbHlzaXMgb24gdGhlIDUwMCBtb3N0IHZhcmlhYmxlIGdlbmVzLiBXZSB3aWxsIHJlZ3Jlc3MgZWFjaCBnZW5lIG9uIHRoZSBwc2V1ZG90aW1lIHZhcmlhYmxlIHdlIGhhdmUgZ2VuZXJhdGVkLCB1c2luZyBhIGdlbmVyYWwgYWRkaXRpdmUgbW9kZWwgKEdBTSkuIFRoaXMgYWxsb3dzIHVzIHRvIGRldGVjdCBub24tbGluZWFyIHBhdHRlcm5zIGluIGdlbmUgZXhwcmVzc2lvbi4gV2UgYXJlIGdvaW5nIHRvIHVzZSBIVkcgd2UgaWRlbnRpZmllZCBpbiB0aGUgcHJldmlvdXMgc3RlcCwgYnV0IHRoaXMgYW5hbHlzaXMgY2FuIGFsc28gYmUgZG9uZSB1c2luZyB0aGUgd2hvbGUgdHJhbnNjcmlwdG9tZS4gCgpgYGB7ciBNYW50b25CTTFfdGVtcEV4cHJHZW5lc19jb21wX3BzZXVkb3RpbWUyfQojIE9ubHkgbG9vayBhdCB0aGUgNTAwIG1vc3QgdmFyaWFibGUgZ2VuZXMgd2hlbiBpZGVudGlmeWluZyB0ZW1wb3JhbGx5IGV4cHJlc3Nlc2QgZ2VuZXMuCiMgSWRlbnRpZnkgdGhlIHZhcmlhYmxlIGdlbmVzIGJ5IHJhbmtpbmcgYWxsIGdlbmVzIGJ5IHRoZWlyIHZhcmlhbmNlLgojIFdlIHdpbGwgdXNlIHRoZSBmaXJzdCBkaWZmdXNpb24gY29tcG9uZW50cyBhcyBhIG1lYXN1cmUgb2YgcHNldWRvdGltZSAKWTwtbG9nMihjb3VudHMoc2NlLnRjZWxsLkJNMSkrMSkKY29sbmFtZXMoWSk8LWNlbGxMYWJlbHMKWTwtWVt0b3AudGNlbGwuQk0xLF0KIyBGaXQgR0FNIGZvciBlYWNoIGdlbmUgdXNpbmcgcHNldWRvdGltZSBhcyBpbmRlcGVuZGVudCB2YXJpYWJsZS4KdCA8LSBlaWdlbnZlY3RvcnMoZG1fdGNlbGxfQk0xKVssIDFdCmdhbS5wdmFsIDwtIGFwcGx5KFksIDEsIGZ1bmN0aW9uKHopewogIGQgPC0gZGF0YS5mcmFtZSh6PXosIHQ9dCkKICB0bXAgPC0gZ2FtKHogfiBsbyh0KSwgZGF0YT1kKQogIHAgPC0gc3VtbWFyeSh0bXApWzRdW1sxXV1bMSw1XQogIHAKfSkKYGBgCgpTZWxlY3QgdG9wIDMwIGdlbmVzIGZvciB2aXN1YWxpemF0aW9uCgpgYGB7ciBNYW50b25CTTFfdGVtcEV4cHJHZW5lc190b3BfcHNldWRvdGltZTJ9CiMgSWRlbnRpZnkgZ2VuZXMgd2l0aCB0aGUgbW9zdCBzaWduaWZpY2FudCB0aW1lLWRlcGVuZGVudCBtb2RlbCBmaXQuCnRvcGdlbmVzIDwtIG5hbWVzKHNvcnQoZ2FtLnB2YWwsIGRlY3JlYXNpbmcgPSBGQUxTRSkpWzE6MzBdICAKYGBgCgpWaXN1YWxpemUgdGhlc2UgZ2VuZXMgaW4gYSBoZWF0bWFwCgpgYGB7ciBNYW50b25CTTFfdGVtcEV4cHJHZW5lc19oZWF0bWFwX3BzZXVkb3RpbWUyfQpoZWF0bWFwZGF0YSA8LSBZW3RvcGdlbmVzLF0KaGVhdG1hcGRhdGEgPC0gaGVhdG1hcGRhdGFbLG9yZGVyKHQsIG5hLmxhc3QgPSBOQSldCnRfYW5uPC1hcy5kYXRhLmZyYW1lKHQpCmNvbG5hbWVzKHRfYW5uKTwtInBzZXVkb3RpbWUiCnBoZWF0bWFwKGhlYXRtYXBkYXRhLCBjbHVzdGVyX3Jvd3MgPSBULCBjbHVzdGVyX2NvbHMgPSBGLCBjb2xvciA9IHBsYXNtYSgyMDApLCBzaG93X2NvbG5hbWVzID0gRiwgYW5ub3RhdGlvbl9jb2wgPSB0X2FubikKYGBgCgpfX1Zpc3VhbGl6ZSBob3cgc29tZSBvZiB0aGUgdGVtcG9yYWxseSBleHByZXNzZWQgZ2VuZXMgY2hhbmdlIGluIHRpbWVfXwoKRm9sbG93aW5nIGluZGl2aWR1YWwgZ2VuZXMgaXMgdmVyeSBoZWxwZnVsIGZvciBpZGVudGlmeWluZyBnZW5lcyB0aGF0IHBsYXkgYW4gaW1wb3J0YW50IHJvbGUgaW4gdGhlIGRpZmZlcmVudGlhdGlvbiBwcm9jZXNzLiBXZSBpbGx1c3RyYXRlIHRoZSBwcm9jZWR1cmUgdXNpbmcgdGhlIEdaTUEgZ2VuZS4gV2UgaGF2ZSBhZGRlZCB0aGUgcHNldWRvdGltZSB2YWx1ZXMgY29tcHV0ZWQgd2l0aCBkZXN0aW55IHRvIHRoZSBjb2xEYXRhIHNsb3Qgb2YgdGhlIFNDRSBvYmplY3QuIEhhdmluZyBkb25lIHRoYXQsIHRoZSBmdWxsIHBsb3R0aW5nIGNhcGFiaWxpdGllcyBvZiB0aGUgc2NhdGVyIHBhY2thZ2UgY2FuIGJlIHVzZWQgdG8gaW52ZXN0aWdhdGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGdlbmUgZXhwcmVzc2lvbiwgY2VsbCBwb3B1bGF0aW9ucyBhbmQgcHNldWRvdGltZS4gCgpgYGB7ciBNYW50b25CTTFfdGVtcEV4cHJHZW5lc19HWk1BX3BzZXVkb3RpbWUyfQpwbG90RXhwcmVzc2lvbihzY2UudGNlbGwuQk0xLAoJICAgICAgICJHWk1BIiwKCSAgICAgICB4ID0gInBzZXVkb3RpbWVfZGVzdGlueV8xIiwgCiAgICAgICAgICAgICAgIHNob3dfdmlvbGluID0gVFJVRSwKICAgICAgICAgICAgICAgc2hvd19zbW9vdGggPSBUUlVFKQpgYGAKCiMjIyBQc2V1ZG90aW1lIGFuYWx5c2lzIGZvciBhbm90aGVyIEhDQSBzYW1wbGUKCmBgYHtyIE1hbnRvbkJNMl9wcmVQcm9jX3BzZXVkb3RpbWUyfQojIHB1bGwgYmFyY29kZXMgZm9yIE1hbnRvbkJNMiAKdmVjLmJjIDwtIGNvbERhdGEoc2NlLnRjZWxsKSAlPiUKICAgIGRhdGEuZnJhbWUoKSAlPiUKICAgIGZpbHRlcihTYW1wbGUuTmFtZSA9PSAiTWFudG9uQk0yIikgJT4lCiAgICBncm91cF9ieShTYW1wbGUuTmFtZSkgJT4lCiAgICBzYW1wbGVfbigxMDAwKSAlPiUKICAgIHB1bGwoQmFyY29kZSkKCiMgY3JlYXRlIGFub3RoZXIgb2JqZWN0IGZvciBNYW50b25CTTIKdG1wSW5kIDwtIHdoaWNoKGNvbERhdGEoc2NlLnRjZWxsKSRCYXJjb2RlICVpbiUgdmVjLmJjKQpzY2UudGNlbGwuQk0yIDwtIHNjZS50Y2VsbFssdG1wSW5kXQoKIyBJZGVudGlmdCBIVkcKZGVjLnRjZWxsLkJNMiA8LSBtb2RlbEdlbmVWYXIoc2NlLnRjZWxsLkJNMikKdG9wLnRjZWxsLkJNMiA8LSBnZXRUb3BIVkdzKGRlYy50Y2VsbC5CTTIsIG49NTAwKQoKIyBleHRyYWN0IG5vcm1hbGl6ZWQgY291bnQgZGF0YSBmb3IgSFZHIAp0Y2VsbC5CTTJfY291bnRzPC1sb2djb3VudHMoc2NlLnRjZWxsLkJNMikKdGNlbGwuQk0yX2NvdW50czwtdChhcy5tYXRyaXgodGNlbGwuQk0yX2NvdW50c1t0b3AudGNlbGwuQk0yLF0pKQpjZWxsTGFiZWxzIDwtIHNjZS50Y2VsbC5CTTIkQmFyY29kZQpyb3duYW1lcyh0Y2VsbC5CTTJfY291bnRzKSA8LSBjZWxsTGFiZWxzCgpkbV90Y2VsbF9CTTIgPC0gRGlmZnVzaW9uTWFwKHRjZWxsLkJNMl9jb3VudHMsbl9wY3MgPSA1MCkKCnRtcCA8LSBkYXRhLmZyYW1lKERDMSA9IGVpZ2VudmVjdG9ycyhkbV90Y2VsbF9CTTIpWywgMV0sCiAgICAgICAgICAgICAgICAgIERDMiA9IGVpZ2VudmVjdG9ycyhkbV90Y2VsbF9CTTIpWywgMl0pCgpnZ3Bsb3QodG1wLCBhZXMoeCA9IERDMSwgeSA9IERDMikpICsKICAgIGdlb21fcG9pbnQoKSArIAogICAgeGxhYigiRGlmZnVzaW9uIGNvbXBvbmVudCAxIikgKyAKICAgIHlsYWIoIkRpZmZ1c2lvbiBjb21wb25lbnQgMiIpICsKICAgIHRoZW1lX2NsYXNzaWMoKQoKIyB0aWR5CnJtKHNjZS50Y2VsbCkKYGBgCgoKCg==